<!DOCTYPE html>
<html lang="en">
<head>
<title>VDO.Ninja Chat Pop-out</title>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta content="utf-8" http-equiv="encoding" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#ffffff" />
<style>
	:root {
		--discord-grey-0: #121212;
		--discord-grey-1: #1e1f22;
		--discord-grey-1a: #1e1f22;
		--discord-grey-2: #232428;
		--discord-grey-3: #2b2d31;
		--discord-grey-4: #2e3035;
		--discord-grey-5: #313338;
		--discord-grey-6: #383a40;
		--discord-grey-7: #404249; 
		--discord-grey-8: #5e6064; 
		--discord-text: hsl( 210 calc(1 * 9.1%) 87.1% /1);
		--baby-blue: #BCF;
	}

	* {
		padding: 0;
		margin: 0;
		border: 0;
		box-sizing: border-box;
	}

	body{
		background-color: var(--discord-grey-0);
		height: 100vh;
		width: 100vw;
	}

	#chatModule{
		display: flex;
		flex-direction: column;
		padding: 5px;
		height: 100%;
		gap: 5px;
		justify-content: flex-end;
	}

	#chatBody {
		display: flex;
		flex-direction: column;
		gap: 5px;
		overflow-y: auto;
	}

	.outMessage, .inMessage {
		display: flex;
		align-items: center;
		justify-content: flex-end;
		gap: 5px;

		font-family: 'Roboto';
		font-size: 14px;
		text-align: right;
		background-color: var(--baby-blue);
		border-radius: 4px;
		padding: 2px 5px;
	}

	.inMessage {
		color: var(--discord-text);
		background-color: var(--discord-grey-7);
	}

	.outMessage .chat_message {
		font-weight: bold;
	}

	::-webkit-input-placeholder {
		color: #ccc !important;
	}

	.controls {
		display: flex;
		flex-direction: row;
		gap: 5px;
		height: 25px;
		flex-shrink: 0;
	}

	.controls input {
		border-radius: 4px;
		flex: 3;
		color: var(--discord-text);
		background-color: var(--discord-grey-5);
		border: 1px solid var(--discord-grey-8);
		padding: 0px 5px;
	}

	.controls button {
		border-radius: 4px;
		flex: 1;
		color: var(--discord-text);
		background-color: var(--discord-grey-2);
		border: 1px solid var(--discord-grey-8);
	}

	/* SCROLL BAR STYLING  */
	::-webkit-scrollbar {
	  width: 12px; 
	}

	::-webkit-scrollbar-track {
	  background-color: var(--discord-grey-1); 
	  border-radius: 4px;
	}

	::-webkit-scrollbar-thumb {
	  background-color: var(--discord-grey-7);
	  border-radius: 4px;
	}

	::-webkit-scrollbar-thumb:hover {
	  background-color: var(--discord-grey-8); 
	}
	
	    .modal {
      display: none;
      position: fixed;
      z-index: 1;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      overflow: auto;
      background-color: rgba(0,0,0,0.4);
    }

    .modal-content {
      background-color: var(--discord-grey-3);
      margin: 1% auto;
      padding: 20px;
      border: 1px solid var(--discord-grey-8);
      width: 80%;
      max-width: 500px;
      border-radius: 5px;
      color: var(--discord-text);
    }

    .modal-content input {
      width: 100%;
      padding: 10px;
      margin: 10px 0;
      border-radius: 4px;
      border: 1px solid var(--discord-grey-8);
      background-color: var(--discord-grey-5);
      color: var(--discord-text);
    }

    .modal-content button {
      padding: 10px 20px;
      margin-top: 10px;
      background-color: var(--discord-grey-2);
      color: var(--discord-text);
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }

    .modal-content button:hover {
      background-color: var(--discord-grey-4);
    }

  </style>
</head>
<body>
<div id="chatModule" >
	<div id="chatBody"> 
		<div class="inMessage" data-translate='welcome-to-vdo-ninja-chat'>
			Welcome to VDO.Ninja! You can send text messages directly to connected peers from here.
		</div>
	</div>
    <div class="controls">
        <input id="chatInput" placeholder="Enter chat message to send here" onkeypress="EnterButtonChat(event)" />
        <button onclick="sendChatMessage()" data-translate='send-chat'>Send</button>
    </div>
</div>

<div id="inputModal" class="modal">
  <div class="modal-content">
    <h2>Enter Chat Details</h2>
    <input type="text" id="roomInput" placeholder="Room ID">
    <input type="text" id="viewInput" placeholder="View ID (if not in a  room)">
    <input type="password" id="passwordInput" placeholder="Password (optional)">
    <button onclick="submitModalInput()">Join Chat</button>
  </div>
</div>

<script>
var messageList = [];
var urlParams = new URLSearchParams(window.location.search);
var bc;
var iframe;
var bid;
var room;
var view;
var label;
var password;
var connected = false;
var connectionTimeout;
async function initializeCommunication() {
	let params = await getDecoded();
	
	//console.log(params);
	
    bid = urlParams.get("id") || params?.broadcastChannelID || "default-channel";
    room = urlParams.get("room") || params?.room || "";
    view = urlParams.get("view") || params?.view || "";
    label = urlParams.get("label") || params?.label || "";
    password = urlParams.get("password") || params?.password;
	
    if (bid && ("BroadcastChannel" in window)) {
        bc = new BroadcastChannel(bid);
        
        bc.onmessage = function(e) {
            if (e.data && e.data.messageList) {
                clearTimeout(connectionTimeout);
                connected = true;
                handleMessage(e.data);
            } else if (e.data){
                handleMessage(e.data);
            }
        };

        bc.postMessage({"loaded": true});

        connectionTimeout = setTimeout(function() {
            if (!connected) {
                console.log("BroadcastChannel connection timed out");
                bc.close();
                loadIframe();
            }
        }, 2000); // Wait for 5 seconds

        bc.onclose = function() {
            console.log("Broadcast channel closed");
            if (connected) {
                window.close();
				connected = false;
            }
        };
    } else {
        loadIframe();
    }
}

async function getDecoded() {

    async function deriveKey(password) {
        const encoder = new TextEncoder();
        try {
            const keyMaterial = await crypto.subtle.importKey(
                'raw',
                encoder.encode(password),
                { name: 'PBKDF2' },
                false,
                ['deriveBits', 'deriveKey']
            );
            
            return await crypto.subtle.deriveKey(
                {
                    name: 'PBKDF2',
                    salt: encoder.encode('salt'),
                    iterations: 100000,
                    hash: 'SHA-256'
                },
                keyMaterial,
                { name: 'AES-GCM', length: 256 },
                true,
                ['encrypt', 'decrypt']
            );
        } catch (error) {
            console.error('Error in deriveKey:', error);
            throw error;
        }
    }

	function decrypt(encryptedText, key) {
	  const textEncoder = new TextEncoder();
	  const encodedKey = textEncoder.encode(key);
	  const encrypted = Uint8Array.from(atob(encryptedText), c => c.charCodeAt(0));
	  
	  const decrypted = encrypted.map((byte, i) => 
		byte ^ encodedKey[i % encodedKey.length]
	  );
	  
	  return new TextDecoder().decode(decrypted);
	}
   
   const ENCRYPTION_KEY = 'your32characterlongencryptionkey!!';
  
	try {
		const urlParams = new URLSearchParams(window.location.search);
		const encodedData = urlParams.get('data');
		if (encodedData) {
			//console.log('Encoded data from URL:', encodedData);
			const decrypted = decrypt(encodedData, ENCRYPTION_KEY);
			const params = JSON.parse(decrypted);
			//console.log('Decoded params:', params);
			return params;
		}
		//console.log('No encoded data found in URL');
		return null;
	} catch (error) {
		//console.error('Error in getDecoded:', error);
		return null;
	}

}

function loadIframe() {

    if (!view && !room) {
        showInputModal();
        return;
    }

    iframe = document.createElement("iframe");
    iframe.style.display = "none";
    iframe.allow = "autoplay";

    var srcString = "./?datamode";
    if (room) srcString += "&scene&room=" + encodeURIComponent(room);
    if (view) srcString += "&view=" + encodeURIComponent(view);
    if (password) srcString += "&password=" + encodeURIComponent(password);
	if (label) srcString += "&label=" + encodeURIComponent(label);

    iframe.src = srcString;
    document.body.appendChild(iframe);
	
    var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
    var eventer = window[eventMethod];
    var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";

    eventer(messageEvent, function (e) {
        if (e.source != iframe.contentWindow) return;
       // console.log(e);
        if ("gotChat" in e.data) {
            handleMessage(e.data.gotChat);
        } else if ("chat" in e.data) {
            handleMessage(e.data.chat);
        }
    });
}


function handleMessage(data) {
    if (data.msg) {
        messageList.push(data);
        messageList = messageList.slice(-100);
        updateMessages(data);
    } else if (data.messageList) {
        messageList = data.messageList;
        updateMessages();
    }
}

function showInputModal() {
    document.getElementById('inputModal').style.display = 'block';
}

function submitModalInput() {
    var room = document.getElementById('roomInput').value;
    var view = document.getElementById('viewInput').value;
    var password = document.getElementById('passwordInput').value;

    if (!room && !view) {
        alert("Please enter either a Room ID or a View ID");
        return;
    }

    document.getElementById('inputModal').style.display = 'none';
    updateURLParams(room, view, password);
    loadIframe();
}

function sendChatMessage() {
    var msg = document.getElementById('chatInput').value;
    msg = sanitize(msg);
    if (msg == "") return;
    
    //console.log(msg);
    if (bc && connected) {
		try {
			bc.postMessage({"msg": msg});
		} catch(e){
			if (iframe) {
				const targetOrigin = new URL(iframe.src).origin || "*";
				iframe.contentWindow.postMessage({ sendChat:msg }, targetOrigin);
			}
		}
    } else if (iframe) {
        const targetOrigin = new URL(iframe.src).origin || "*";
		iframe.contentWindow.postMessage({ sendChat:msg }, targetOrigin);
    }
    
    document.getElementById('chatInput').value = "";
    
    messageList.push({"msg": msg, type: "sent", time: new Date()});
    messageList = messageList.slice(-100);
    updateMessages({"msg": msg, type: "sent", time: new Date()});
}

function sanitize(string) {
	var temp = document.createElement('div');
	temp.textContent = string;
	return temp.innerHTML;
}

function decodeHTML(value) {
	if (value === null || value === undefined) {
		return "";
	}
	var temp = document.createElement("textarea");
	temp.innerHTML = value;
	return temp.value;
}

function replaceURLs(message) {
	if (message === undefined || message === null) {
		return "";
	}
	var original = decodeHTML(String(message));
	var urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
	var result = "";
	var lastIndex = 0;
	var match;
	while ((match = urlRegex.exec(original)) !== null) {
		result += sanitize(original.slice(lastIndex, match.index));
		var url = match[0];
		var trailing = "";
		while (/[.,;!:\*\?)]$/.test(url)) {
			trailing = url.slice(-1) + trailing;
			url = url.slice(0, -1);
		}
		if (url) {
			var hyperlink = url;
			if (!/^https?:\/\//i.test(hyperlink)) {
				hyperlink = "http://" + hyperlink;
			}
			var display = url.length > 35 ? url.substring(0, 35) + "..." : url;
			result += '<a href="' + sanitize(hyperlink) + '" title="Click to open the link in a new tab" target="_blank" rel="noopener noreferrer">' + sanitize(display) + "</a>";
		} else {
			result += sanitize(match[0]);
		}
		if (trailing) {
			result += sanitize(trailing);
		}
		lastIndex = match.index + match[0].length;
	}
	if (lastIndex < original.length) {
		result += sanitize(original.slice(lastIndex));
	}
	return result;
}

function EnterButtonChat(event){
	 // Number 13 is the "Enter" key on the keyboard
	var key = event.which || event.keyCode;
	if (key  === 13) {
		// Cancel the default action, if needed
		event.preventDefault();
		// Trigger the button element with a click
		sendChatMessage();
	}
}

function timeSince(date) {

  var seconds = Math.floor((new Date() - date) / 1000);

  var interval = seconds / 31536000;

  if (interval > 1) {
    return Math.floor(interval) + " years";
  }
  interval = seconds / 2592000;
  if (interval > 1) {
    return Math.floor(interval) + " months";
  }
  interval = seconds / 86400;
  if (interval > 1) {
    return Math.floor(interval) + " days";
  }
  interval = seconds / 3600;
  if (interval > 1) {
    return Math.floor(interval) + " hours";
  }
  interval = seconds / 60;
  if (interval > 1) {
    return Math.floor(interval) + " minutes";
  }
  return "Seconds ago";
}


function updateMessages(message = false){
	if (message){
		var time = timeSince(message.time);
		var msg = document.createElement("div");
		////// KEEP THIS IN /////////
		//console.log(message.msg); // Display Recieved messages for View-Only clients.
		/////////////////////////////
		var label = message.label ? decodeHTML(message.label) : "";
		var labelText = sanitize(label);
		var safeMessage = replaceURLs(message.msg);
		var safeTime = sanitize(time);

		if (message.type == "sent"){
			msg.innerHTML = "<span class='chat_message chat_sent'>"+safeMessage + "  </span><i><small> <small>- "+safeTime+"</small></small></i><span style='display:none'>"+labelText+"</span>";
			msg.classList.add("outMessage");
		} else if (message.type == "recv"){
			msg.innerHTML = label+"<span class='chat_message chat_recv'>"+safeMessage + " </span><i><small> <small>- "+safeTime+"</small></small></i>";
			msg.classList.add("inMessage");
		} else if (message.type == "action"){
			msg.innerHTML = label+"<span class='chat_message chat_action'>"+safeMessage + " </span><i><small> <small>- "+safeTime+"</small></small></i>";
			msg.classList.add("actionMessage");
		} else if (message.type == "alert"){
			msg.innerHTML = "<span class='chat_message chat_alert'>"+safeMessage + " </span><i><small> <small>- "+safeTime+"</small></small></i>";
			msg.classList.add("inMessage");
		} else {
			msg.innerHTML = "<span class='chat_message chat_other'>"+safeMessage + "  </span><i><small> <small>- "+safeTime+"</small></small></i>";
			msg.classList.add("inMessage");
		}
		document.getElementById("chatBody").appendChild(msg);
	} else {
		document.getElementById("chatBody").innerHTML = "";
		for (i in messageList){
			var time = timeSince(messageList[i].time);
			var msg = document.createElement("div");
			////// KEEP THIS IN /////////
			//console.log(messageList[i].msg); // Display Recieved messages for View-Only clients.
			/////////////////////////////
			var label = messageList[i].label ? decodeHTML(messageList[i].label) : "";
			var labelText = sanitize(label);
			
			var message = replaceURLs(messageList[i].msg);
			var safeTime = sanitize(time);
			
			if (messageList[i].type == "sent"){
				msg.innerHTML = "<span class='chat_message chat_sent'>"+message + "  </span><i><small> <small>- "+safeTime+"</small></small></i><span style='display:none'>"+labelText+"</span>";
				msg.classList.add("outMessage");
			} else if (messageList[i].type == "recv"){
				msg.innerHTML = label+"<span class='chat_message chat_recv'>"+message + " </span><i><small> <small>- "+safeTime+"</small></small></i>";
				msg.classList.add("inMessage");
			} else if (messageList[i].type == "action"){
				msg.innerHTML = label+"<span class='chat_message chat_action'>"+message + " </span><i><small> <small>- "+safeTime+"</small></small></i>";
				msg.classList.add("actionMessage");
			} else if (messageList[i].type == "alert"){
				msg.innerHTML = "<span class='chat_message chat_alert'>"+message + " </span><i><small> <small>- "+safeTime+"</small></small></i>";
				msg.classList.add("inMessage");
			} else {
				msg.innerHTML = "<span class='chat_message chat_other'>"+message + "  </span><i><small> <small>- "+safeTime+"</small></small></i>";
				msg.classList.add("inMessage");
			}
			
			document.getElementById("chatBody").appendChild(msg);
		}
	}
	//if (chatUpdateTimeout){
	//	clearInterval(chatUpdateTimeout);
	//}
	document.getElementById("chatBody").scrollTop = document.getElementById("chatBody").scrollHeight;
	//chatUpdateTimeout = setTimeout(function(){updateMessages()},60000);
}

initializeCommunication();

</script>
</body>
</html>
